package vg.modules.opener.system;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Stack;
import java.util.Date;
import java.text.DateFormat;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import org.xml.sax.helpers.DefaultHandler;
import org.apache.commons.lang.StringEscapeUtils;
import vg.core.AModel;
import vg.core.IProgressTask;
import vg.core.VisualGraph;
import vg.core.exception.CoreException;
import vg.core.exception.EnumCriticalityException;
import vg.core.storableGraph.StorableAttribute;
import vg.core.storableGraph.StorableEdge;
import vg.core.storableGraph.StorableGraph;
import vg.core.storableGraph.StorableSubGraph;
import vg.core.storableGraph.StorableVertex;
/**
* This class reads graph in GraphML file format. It's port from
* <a href="http://http://prefuse.org//">prefuse visualization toolkit</a>.<BR>
* GraphML is an XML format supporting graph structure and typed data
* schemas for both nodes and edges. For more information about the
* format, please see the
* <a href="http://graphml.graphdrawing.org/">GraphML home page</a>.
*
* @author dkolbin
*/
public class GraphMLDecoder implements IDecoder {
/**
* @see vg.modules.opener.system.IDecoder
*/
public Integer decode(String fileName, AModel model, String graphName) throws CoreException{
try {
SAXParserFactory factory = SAXParserFactory.newInstance();
SAXParser saxParser = factory.newSAXParser();
GraphMLHandler handler = new GraphMLHandler(model, graphName);
File f = new File(fileName);
final long fileSize = (int) f.length();
final FileInputStream fis = new FileInputStream(f);
if (fis != null)
fis.getChannel().position();
IProgressTask task = new IProgressTask() {
@Override
public long getValue() {
if (fis.getChannel() != null)
try {
return fis.getChannel().position();
} catch (IOException e) {
}
return getLength();
}
@Override
public long getLength() {
return fileSize;
}
};
VisualGraph.progressManager.addTask(task);
saxParser.parse(fis, handler);
VisualGraph.progressManager.removeTask(task);
return handler.getGraphID();
} catch (CoreException e) {
throw e;
} catch (ParserConfigurationException e) {
VisualGraph.log.printStackTrace(e.getStackTrace());
throw new CoreException(e.getMessage(), EnumCriticalityException.FAILED);
} catch (SAXParseException e) {
VisualGraph.log.printStackTrace(e.getStackTrace());
throw new CoreException(fileName + ":" + e.getLineNumber() + ":" + e.getColumnNumber()
+ " " + e.getMessage(), EnumCriticalityException.FAILED);
} catch (SAXException e) {
VisualGraph.log.printStackTrace(e.getStackTrace());
throw new CoreException(fileName + ":" + e.getMessage(), EnumCriticalityException.FAILED);
} catch (FileNotFoundException e) {
VisualGraph.log.printStackTrace(e.getStackTrace());
throw new CoreException("File " + fileName + " don't found", EnumCriticalityException.FAILED);
} catch (IOException e) {
VisualGraph.log.printStackTrace(e.getStackTrace());
throw new CoreException(e.getMessage(), EnumCriticalityException.FAILED);
}
}
/**
* String tokens used in the GraphML format.
*/
private interface Tokens {
public static final String ID = "id";
public static final String GRAPH = "graph";
public static final String EDGEDEF = "edgedefault";
public static final String DIRECTED = "directed";
public static final String UNDIRECTED = "undirected";
public static final String KEY = "key";
public static final String FOR = "for";
public static final String ALL = "all";
public static final String ATTRNAME = "attr.name";
public static final String ATTRTYPE = "attr.type";
public static final String DEFAULT = "default";
public static final String NODE = "node";
public static final String EDGE = "edge";
public static final String SOURCE = "source";
public static final String TARGET = "target";
public static final String DATA = "data";
public static final String TYPE = "type";
public static final String INT = "int";
public static final String INTEGER = "integer";
public static final String LONG = "long";
public static final String FLOAT = "float";
public static final String DOUBLE = "double";
public static final String REAL = "real";
public static final String BOOLEAN = "boolean";
public static final String STRING = "string";
public static final String DATE = "date";
}
/**
* A SAX Parser for GraphML data files.
*/
private static class GraphMLHandler extends DefaultHandler implements Tokens {
private AModel model;
private String graphName;
private static final String SRC = SOURCE;
private static final String TRG = TARGET;
private String m_graphid;
private StorableGraph m_graph = null;
// schema parsing
private String m_id;
private String m_for;
private String m_name;
private String m_type;
private String m_dflt;
private String error;
private StringBuffer m_sbuf = new StringBuffer();
// node,edge,data parsing
private String m_key;
private Stack<NestedGraph> lastGraph = new Stack<NestedGraph>();
private HashMap<String,Attr> m_vertData = new HashMap<String, Attr>();
private HashMap<String,Attr> m_edjeData = new HashMap<String, Attr>();
private boolean m_directed = false;
private boolean inSchema;
private Stack<StorableVertex> lastVertex = new Stack<StorableVertex>();
private PseudoEdge lastEdge = null;
private Stack<ArrayList <Attr> > lastProperties = new Stack<ArrayList <Attr> >();
private boolean graphSection = false;
private ArrayList<Integer> subgraphIDs = new ArrayList<Integer>();
class PseudoEdge {
String left;
String right;
String id;
ArrayList<Attr> properties;
PseudoEdge(String aLeft, String aRight, String id) {
left = aLeft;
right = aRight;
properties = new ArrayList <Attr>();
this.id = id;
}
public void addAttribute(String key, Class<?> aType, String value)
{
properties.add(new Attr(key, aType, value));
}
}
class Attr {
String name;
Class<?> type;
Object value;
Attr(String aName, Class<?> aType, Object aValue)
{
name = aName;
type = aType;
value = aValue;
}
}
private class NestedGraph {
boolean directed;
String id;
ArrayList<StorableVertex> nestedVertex;
ArrayList<PseudoEdge> nestedEdge;
String name;
private HashMap<String, StorableVertex> m_nodeMap = new HashMap<String, StorableVertex>();
NestedGraph(String id, boolean directed) {
this.id = id;
this.directed = directed;
nestedVertex = new ArrayList<StorableVertex>();
nestedEdge = new ArrayList<PseudoEdge>();
}
void addVertex(String id) {
lastVertex.push(new StorableVertex(id));
m_nodeMap.put(id, lastVertex.lastElement());
nestedVertex.add(lastVertex.lastElement());
}
}
GraphMLHandler(AModel model, String graphName)
{
this.model = model;
this.graphName = graphName;
}
@Override
public void startDocument() {
inSchema = true;
}
@Override
public void endDocument() throws SAXException {
// now create the graph
int root = subgraphIDs.get(subgraphIDs.size() - 1);
m_graph = new StorableGraph(graphName, null, root);
model.addStorableGraph(m_graph, subgraphIDs);
}
@Override
public void startElement(String namespaceURI, String localName,
String qName, Attributes atts) {
// first clear the character buffer
m_sbuf.delete(0, m_sbuf.length());
if (qName.equals(GRAPH)) {
// parse directedness default
String edef = atts.getValue(EDGEDEF);
m_directed = DIRECTED.equalsIgnoreCase(edef);
m_graphid = atts.getValue(ID);
lastProperties.push(new ArrayList<Attr>());
lastGraph.push(new NestedGraph(m_graphid, m_directed));
graphSection = true;
} else if (qName.equals(KEY)) {
if (!inSchema) {
error("\"" + KEY + "\" elements can not"
+ " occur after the first node or edge declaration.");
}
m_for = atts.getValue(FOR);
m_id = atts.getValue(ID);
m_name = atts.getValue(ATTRNAME);
m_type = atts.getValue(ATTRTYPE);
} else if (qName.equals(NODE)) {
inSchema = false;
graphSection = false;
String id = atts.getValue(ID);
lastGraph.lastElement().addVertex(id);
lastProperties.push(new ArrayList<Attr>());
for(Attr a:m_vertData.values())
{
if (a.value != null)
lastProperties.lastElement().add(new Attr(a.name,a.type, a.value));
}
} else if (qName.equals(EDGE)) {
inSchema = false;
graphSection = false;
// do not use the id value
// String id = atts.getValue(ID);
// if ( id != null ) {
// if ( !m_edges.canGetString(ID) )
// m_edges.addColumn(ID, String.class);
// m_edges.setString(m_row, ID, id);
// }
String id = atts.getValue(ID);
lastEdge = new PseudoEdge(atts.getValue(SRC), atts.getValue(TRG), id);
/*graphs.get(lastGraph.lastElement()).nestedEdge.add(
(new PseudoEdge(atts.getValue(SRC), atts.getValue(TRG))));
*/
// currently only global directedness is used
// ignore directed edge value for now
// String dir = atts.getValue(DIRECTED);
// boolean d = m_directed;
// if ( dir != null ) {
// d = dir.equalsIgnoreCase("false");
// }
// m_edges.setBoolean(m_row, DIRECTED, d);
lastProperties.push(new ArrayList<Attr>());
for(Attr a:m_edjeData.values())
{
if (a.value != null)
lastProperties.lastElement().add(new Attr(a.name,a.type, a.value));
}
} else if (qName.equals(DATA)) {
m_key = atts.getValue(KEY);
}
}
@Override
public void endElement(String namespaceURI,
String localName, String qName) throws SAXException {
if (qName.equals(DEFAULT)) {
// value is in the buffer
m_dflt = m_sbuf.toString();
} else if (qName.equals(KEY)) {
// time to add to the proper schema(s)
addToSchema();
} else if (qName.equals(GRAPH)) {
NestedGraph e = lastGraph.pop();
ArrayList<StorableEdge> edges = new ArrayList<StorableEdge>();
for (int j = 0; j < e.nestedEdge.size(); j++) {
String src = e.nestedEdge.get(j).left;
if (!e.m_nodeMap.containsKey(src)) {
VisualGraph.log.printInfo("Skip edge between different nested graphs");
continue;
}
String trg = e.nestedEdge.get(j).right;
if (!e.m_nodeMap.containsKey(trg)) {
VisualGraph.log.printInfo("Skip edge between different nested graphs");
continue;
}
StorableEdge ed = new StorableEdge(e.m_nodeMap.get(src), e.m_nodeMap.get(trg),e.nestedEdge.get(j).id);
for (Attr a:e.nestedEdge.get(j).properties) {
ed.addStorableAttribute(new StorableAttribute(a.name,(String) a.value, a.type.toString()));
}
edges.add(ed);
}
if (e.name == "")
e.name = e.id;
StorableSubGraph ssg = new StorableSubGraph(e.id, e.name, e.nestedVertex, edges, e.directed);
if (lastVertex.size() != 0) {
lastVertex.lastElement().setInnerGraph(ssg.getStorableId());
}
this.model.addStorableSubGraph(ssg);
subgraphIDs.add(ssg.getStorableId());
lastProperties.pop();
graphSection = false;
} else if (qName.equals(NODE)) {
ArrayList<Attr> p = lastProperties.pop();
for (Attr a: p) {
if(lastVertex.lastElement().addStorableAttribute(new StorableAttribute(a.name,
a.value.toString(), a.type.toString())) == false)
{
VisualGraph.log.printError("[" + this.getClass().getName() + ".endElement] [BAD] Can't add attribute to vertex");
}
}
lastVertex.pop();
graphSection = true;
} else if (qName.equals(EDGE)) {
ArrayList<Attr> p = lastProperties.pop();
for (Attr a: p) {
lastEdge.addAttribute(a.name, a.type, a.value.toString());
}
lastGraph.lastElement().nestedEdge.add(lastEdge);
graphSection = true;
} else if (qName.equals(DATA)) {
// value is in the buffer
String value = StringEscapeUtils.unescapeJava(m_sbuf.toString());
if (graphSection)
{
Attr a = m_vertData.get(m_key);
if (a == null)
a = m_edjeData.get(m_key);
if (a == null)
error("found data tag in wrong position");
if (a.name.toLowerCase().compareTo("name") == 0)
{
lastGraph.lastElement().name = value;
}
} else {
Class<?> type;
Attr a = m_vertData.get(m_key);
if (a == null)
a = m_edjeData.get(m_key);
if (a == null)
error("found data tag in wrong position");
try {
Object val = parse(value, a.type);
boolean newattr = true;
for (Attr attr :lastProperties.lastElement()) {
if (attr.name.equals(a.name)) {
attr.value = val;
newattr = false;
}
}
if (newattr)
lastProperties.lastElement().add(new Attr(a.name, a.type, val));
} catch (ParseException ex) {
Logger.getLogger(GraphMLDecoder.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
}
// --------------------------------------------------------------------
@Override
public void characters(char[] ch, int start, int length) throws SAXException {
m_sbuf.append(ch, start, length);
}
private void addToSchema() {
if (m_name == null || m_name.length() == 0) {
error("Empty " + KEY + " name.");
}
if (m_type == null || m_type.length() == 0) {
error("Empty " + KEY + " type.");
}
Class<?> type = parseType(m_type);
Object dflt = null;
try {
dflt = m_dflt == null ? null : parse(m_dflt, type);
} catch (ParseException ex) {
Logger.getLogger(GraphMLDecoder.class.getName()).log(Level.SEVERE, null, ex);
}
if (m_for == null || m_for.equals(ALL)) {
m_vertData.put(m_id, new Attr(m_name,type, dflt));
m_edjeData.put(m_id, new Attr(m_name,type, dflt));
} else if (m_for.equals(NODE)) {
m_vertData.put(m_id, new Attr(m_name,type, dflt));
} else if (m_for.equals(EDGE)) {
m_edjeData.put(m_id, new Attr(m_name,type, dflt));
} else {
error("Unrecognized \"" + FOR + "\" value: " + m_for);
}
m_dflt = null;
}
private Class<?> parseType(String type) {
type = type.toLowerCase();
if (type.equals(INT) || type.equals(INTEGER)) {
return Integer.class;
} else if (type.equals(LONG)) {
return Long.class;
} else if (type.equals(FLOAT)) {
return Float.class;
} else if (type.equals(DOUBLE) || type.equals(REAL)) {
return Double.class;
} else if (type.equals(BOOLEAN)) {
return Boolean.class;
} else if (type.equals(STRING)) {
return String.class;
} else if (type.equals(DATE)) {
return Date.class;
} else {
error("Unrecognized data type: " + type);
return null;
}
}
private Object parse(String s, Class<?> type) throws ParseException {
if (type == Integer.class) {
return Integer.valueOf(s);
} else if (type == Long.class) {
return Long.valueOf(s);
} else if (type == Float.class) {
return Float.valueOf(s);
} else if (type == Double.class) {
return Double.valueOf(s);
} else if (type == Boolean.class) {
return Boolean.valueOf(s);
} else if (type == String.class) {
return s;
} else if (type == Date.class) {
return DateFormat.getInstance().parse(s);
}
return type.cast(s);
}
public Integer getGraphID() throws CoreException{
if (m_graph == null)
throw new CoreException(error, EnumCriticalityException.FAILED);
return m_graph.getStorableId();
}
private void error(String s) {
throw new RuntimeException(s);
}
} // end of inner class GraphMLHandler
} // end of class XMLGraphReader